package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.atguigu.gmall.search.service.SearchService;
import com.atguigu.gmall.search.vo.Goods;
import com.atguigu.gmall.search.vo.SearchAttrValueResponseVo;
import com.atguigu.gmall.search.vo.SearchParamVo;
import com.atguigu.gmall.search.vo.SearchResponseVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.query.HighlightQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class SearchServiceImpl implements SearchService {
//    @Autowired
//    ElasticsearchRestTemplate restTemplate;
    // 原生的客户端 可以输出它生成的DSL语句
    @Autowired
    RestHighLevelClient client;
    //根据条件从es中查询页面需要的数据
    //页面Goods数据的集合，分页(页码 每页几条记录 总记录条数)，分类列表，品牌列表，attrValues列表
    @Override
    public SearchResponseVo search(SearchParamVo paramVo) {//paramVo完整DSL需要的参数
        try {
            SearchRequest searchRequest = getSearchRequest(paramVo);
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            //解析查询的结果集： 查询es数据的页面最终显示查询的结果集
            //  响应数据模型：
            SearchResponseVo searchResponseVo = new SearchResponseVo();
            searchResponseVo.setPageNum(paramVo.getPageNum());
            searchResponseVo.setPageSize(paramVo.getPageSize());
            //1、查询的文档数据：总文档数量+分页数据集合+高亮
            SearchHits hits = response.getHits();
            Long total = hits.getTotalHits().value;//获取文档的总记录条数
            searchResponseVo.setTotal(total);
            SearchHit[] searchHits = hits.getHits();
            if(ArrayUtils.isNotEmpty(searchHits)){
                ObjectMapper mapper = new ObjectMapper();
                List<Goods> goodsList = new ArrayList<>();
                for (SearchHit searchHit : searchHits) {
                    //每一个searchHit代表查询的一个文档
                    //获取searchHit的source转为Goods对象
//                    searchHit.getSourceAsString(); 将souce转为json字符串获取   new Gson().fromJson(json,Goods.class);
//                    searchHit.getSourceAsMap();   将source转为Map获取
                    //Map转为 java对象：map的key需要和java的属性名一致
                    // Jackson提供了工具类ObjectMapper： 将map转为json  再将json转为对象
                    Map<String, Object> map = searchHit.getSourceAsMap();
                    Goods goods = mapper.convertValue(map, Goods.class);
                    //高亮
                    HighlightField field = searchHit.getHighlightFields().get("title");
                    if(field!=null &&field.getFragments().length>0 ){
                        String highLightTitle = field.getFragments()[0].toString();
                        goods.setTitle(highLightTitle);
                    }

                    goodsList.add(goods);
                }
                searchResponseVo.setGoodsList(goodsList);

            }
            //2、聚合：品牌 分类 检索属性和值
            Map<String, Aggregation> map = response.getAggregations().asMap();
            //ParsedStringTerms 对字符串的数据进行聚合返回的聚合结果类型
            // ParsedLongTerms:对数值型的数据进行聚合 返回的聚合结果类型
            //品牌
            ParsedLongTerms brandAgg = (ParsedLongTerms) map.get("brandAgg");
            //获取聚合后的品牌桶
            List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
            List<BrandEntity> brandEntities = new ArrayList<>();
            for (Terms.Bucket bucket : brandAggBuckets) {
                BrandEntity brandEntity = new BrandEntity();
                //每个bucket就是一个品牌： 桶内有品牌id，它内部再次聚合的子桶 有品牌标题 和品牌logo
                String brandId = bucket.getKey().toString();
                ParsedStringTerms brandNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("brandNameAgg");
                String brandName = brandNameAgg.getBuckets().get(0).getKey().toString();
                ParsedStringTerms brandLogoAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("brandLogoAgg");
                String brandLogo = brandLogoAgg.getBuckets().get(0).getKey().toString();
                brandEntity.setLogo(brandLogo);
                brandEntity.setId(Long.parseLong(brandId));
                brandEntity.setName(brandName);
                brandEntities.add(brandEntity);
            }
            searchResponseVo.setBrands(brandEntities);
            //分类
            ParsedLongTerms cateAgg = (ParsedLongTerms) map.get("cateAgg");
            List<? extends Terms.Bucket> cateAggBuckets = cateAgg.getBuckets();
            List<CategoryEntity> categories = new ArrayList<>();
            for (Terms.Bucket bucket : cateAggBuckets) {
                CategoryEntity categoryEntity = new CategoryEntity();
                String cateId = bucket.getKey().toString();//分类id
                ParsedStringTerms categoryNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("categoryNameAgg");
                String cateName = categoryNameAgg.getBuckets().get(0).getKey().toString();
                categoryEntity.setId(Long.parseLong(cateId));
                categoryEntity.setName(cateName);
                categories.add(categoryEntity);
            }
            searchResponseVo.setCategories(categories);
            //检索属性和值
            ParsedNested searchAttrAgg = (ParsedNested) map.get("searchAttrAgg");
            //获取嵌套属性的子聚合
            ParsedLongTerms attrIdAgg = (ParsedLongTerms) searchAttrAgg.getAggregations().asMap().get("attrIdAgg");
            List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
            List<SearchAttrValueResponseVo> searchAttrValueResponseVos = new ArrayList<>();
            for (Terms.Bucket bucket : attrIdAggBuckets) {
                //每次遍历得到一个检索属性
                String attrId = bucket.getKey().toString();
                //每个attrId对应一个attrName
                ParsedStringTerms attrNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("attrNameAgg");
                String attrName = attrNameAgg.getBuckets().get(0).getKey().toString();
                ParsedStringTerms attrValueAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("attrValueAgg");
                List<String> attrValues = new ArrayList<>();
                for (Terms.Bucket attrValueAggBucket : attrValueAgg.getBuckets()) {
                    //遍历attrValue的桶  每次遍历得到attrId的一个值
                    String attrValue = attrValueAggBucket.getKey().toString();
                    attrValues.add(attrValue);
                }
                SearchAttrValueResponseVo searchAttrValueResponseVo = new SearchAttrValueResponseVo();
                searchAttrValueResponseVo.setAttrId(Long.parseLong(attrId));
                searchAttrValueResponseVo.setAttrName(attrName);
                searchAttrValueResponseVo.setAttrValues(attrValues);

                searchAttrValueResponseVos.add(searchAttrValueResponseVo);
            }
            searchResponseVo.setAttrValues(searchAttrValueResponseVos);

           // System.out.println(searchResponseVo);
            return searchResponseVo;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }

    public SearchRequest getSearchRequest(SearchParamVo paramVo) {
        //根据条件生成DSL语句从es中查询数据
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//DSL构建器
        //1、设置 查询条件：匹配 和过滤查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        String keyword = paramVo.getKeyword();
        if(StringUtils.isNotBlank(keyword)){
            //title匹配查询
            boolQuery.must(QueryBuilders.matchQuery("title",keyword));
        }
        //是否有库存
        Boolean store = paramVo.getStore();
        if(store!=null){
            boolQuery.filter(QueryBuilders.termQuery("store",store));
        }
        //价格区间
        Double priceFrom = paramVo.getPriceFrom();
        Double priceTo = paramVo.getPriceTo();
        if(priceFrom!=null || priceTo!=null){
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
            if(priceFrom!=null){
                rangeQuery.gte(priceFrom);
            }
            if(priceTo!=null){
                rangeQuery.lte(priceTo);
            }
            boolQuery.filter(rangeQuery);
        }
        //品牌id集合
        List<Long> brandIds = paramVo.getBrandId();
        if(!CollectionUtils.isEmpty(brandIds)){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",brandIds));
        }
        //分类id
        Long cid = paramVo.getCid();
        if(cid!=null){
            boolQuery.filter(QueryBuilders.termQuery("categoryId",cid));
        }
        //检索属性和值集合
        //&props=5:高通-麒麟&props=6:骁龙865-硅谷1000
        List<String> props = paramVo.getProps();
        if(!CollectionUtils.isEmpty(props)){
            for (String prop : props) {//每次遍历的prop解析后生成一个嵌套查询 都设置到boolQuery的filter中
                String[] attr = StringUtils.split(prop, ":");
                if(ArrayUtils.isNotEmpty(attr)&&attr.length==2){
                    //attr数组长度必须为2：  索引1代表attrId，索引2代表选中的多个attrValue
                    String attrId = attr[0];
                    String[] attrValues = StringUtils.split(attr[1], "-");
                    //设置为boolQuery嵌套查询的 过滤查询的条件
                    //参数1：嵌套查询 嵌套属性名  ，参数2：嵌套查询的子DSL语句   参数3：嵌套查询索引的评分方式
                    BoolQueryBuilder query = QueryBuilders.boolQuery();
                    query.must(QueryBuilders.termQuery("searchAttrValues.attrId",attrId));
                    query.must(QueryBuilders.termsQuery("searchAttrValues.attrValue",attrValues));
                    boolQuery.filter(QueryBuilders.nestedQuery("searchAttrValues",query, ScoreMode.None));
                }
            }
        }
        sourceBuilder.query(boolQuery);
        //2、分页
        // from设置的值相当于查询数据的起始索引:需要手动根据页码+pageSize计算查询数据的起始索引
        sourceBuilder.from((paramVo.getPageNum()-1)*paramVo.getPageSize());
        sourceBuilder.size(paramVo.getPageSize());
        //3、排序
        //0-默认，得分降序；1-按价格升序；2-按价格降序；3-按创建时间降序；4-按销量降序
        Integer sort = paramVo.getSort();
        SortBuilder sortBuilder;
        switch(sort){
            case 1:
                sortBuilder = SortBuilders.fieldSort("price").order(SortOrder.ASC);
                break;
            case 2:
                sortBuilder = SortBuilders.fieldSort("price").order(SortOrder.DESC);
                break;
            case 3:
                sortBuilder = SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
                break;
            case 4:
                sortBuilder = SortBuilders.fieldSort("sales").order(SortOrder.DESC);
                break;
            default:
                //默认
                sortBuilder = SortBuilders.fieldSort("_score").order(SortOrder.DESC);
        }
        sourceBuilder.sort(sortBuilder);
        //4、高亮
        sourceBuilder.highlighter(new HighlightBuilder()
                .field("title")
                .preTags("<font style='color:red;'>")
                .postTags("</font>"));
        //5、结果集字段过滤
        sourceBuilder.fetchSource(new String[]{"skuId","defaultImage",
                "price","title","subtitle"},null);

        //6、聚合
        //品牌聚合： 每一个品牌id 标题 logo都是唯一的
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg")
            .field("brandId")
            .subAggregation(AggregationBuilders.terms("brandLogoAgg").field("logo"))
            .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")));
        //分类聚合：每一个分类的id 标题都是唯一的
        sourceBuilder.aggregation(AggregationBuilders.terms("cateAgg")
            .field("categoryId")
            .subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));
        //嵌套属性： 检索属性和值的聚合 每一个检索属性id和标题唯一 值有多个(  attrId4   attrName运存  attrValue4G 8G 12G  )
        //参数1：聚合后的桶名  参数2：聚合属性的路径
        sourceBuilder.aggregation(AggregationBuilders.nested("searchAttrAgg","searchAttrValues")
            .subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrValues.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrValues.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrValues.attrValue"))));

        System.out.println(sourceBuilder);
        SearchRequest searchRequest = new SearchRequest("goods");//指定索引库
        searchRequest.source(sourceBuilder);
        return searchRequest;
    }
}
